package com.pic.mgt.haishin.executer.tar;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.io.IOUtils;

import com.pic.mgt.haishin.executer.InvalidTerminalException;
import com.pic.mgt.utils.FilePathUtils;

public class HaishinDeTarFileService implements DeTarFile {
	
	private boolean usingGZipDecompress = false;
	private boolean isInterruptDeTarAction = false;
	private EntryListener entryListener;
	private DeTarFileChecker deTarFileChecker;	
	private boolean deTaring = false;
	private int bufferLength = 1024; // 解壓縮過程中的 buffer size	
	
	@Override
	public synchronized void deTarFile(File tarFile)
												throws DeTarFileException {
		if (null == this.deTarFileChecker) {
			throw new DeTarFileException("Detar file terminal checker has not been set yes.");
		}
		
		// 設定開始解 tar 旗標
		deTaring = true;
		
		// 設定解tar過程中是否中斷執行的旗標
		isInterruptDeTarAction = false;
		
		File deTarDestFolder = null;
		TarArchiveInputStream tarArchiveIS = null; 
		BufferedOutputStream bos = null;
        try {
        	// 先取得 TarArchiveInputStream
        	// throws IOException
        	tarArchiveIS = getTarArchiveInputStream(tarFile);
        	
            TarArchiveEntry entry = null;
            boolean entryIsRoot;
            boolean entryIsDirectory;
            
            // throws IOException, IllegalArgumentException
            while ((entry = tarArchiveIS.getNextTarEntry()) != null) {
            	if (this.isInterruptDeTarAction) {
            		throw new DeTarFileException("De-tar process for file '" + tarFile.getAbsolutePath()
							+ "' has been interrupted.");
            	}
            	
            	String entryName = entry.getName();
            	entryIsRoot = FilePathUtils.isRoot(entryName);
            	entryIsDirectory = entry.isDirectory();
            	
            	// 只針對最外層的檔案做解tar，其餘忽略
            	if (!entryIsRoot || entryIsDirectory) {
            		continue;
            	}
           	
            	// 對將要解出的檔案交由 DeTarFileTerminalChecker進行檔案的端未確認
            	try {
            		// throws InvalidTerminalException
					deTarDestFolder = this.deTarFileChecker.checkDeTarFile(entryName);
					if (null == deTarDestFolder) {
						// 表示要忽略檔案，不做解tar也不做通知。
						continue;
					}
				} catch (InvalidTerminalException e) {
					// 表示將要解的檔案包含錯誤的端末ID，不解tar但進行通知。
					deTarNotify(new File(entryName), entryName, DeTarResult.EntryBeFiltered);
					continue;
				}
            	
            	
            	// 開始解出檔案
				
				File toBeDeTaredFile = new File(deTarDestFolder.getAbsolutePath(),
												entryName);
				
                try {  
                    byte[] buffer = new byte[bufferLength]; 
                    // throws FileNotFoundException
                    bos = new BufferedOutputStream(new FileOutputStream(toBeDeTaredFile),
                    							   bufferLength);  
                    int len;  
                    long size = entry.getSize();  
                    while (size > 0) {  
                        if (size < bufferLength) {  
                        	// IOException
                            len = tarArchiveIS.read(buffer, 0, (int) size);  
                            size -= len;  
                        } else { 
                        	// IOException
                            len = tarArchiveIS.read(buffer);  
                            size -= len;  
                        }
                        // IOException
                        bos.write(buffer, 0, len);                            
                    }  
                } finally {  
                	bos.close();
                } 
                
    			// 解 tar 結果通知
    			deTarNotify(toBeDeTaredFile, entryName,DeTarResult.EntryIsCompleted);
            }
            
            // throws IOException
            tarArchiveIS.close();
            
            tarArchiveIS = null;
            bos = null;
        } catch(IllegalArgumentException e1) {
        	throw new DeTarFileException (
        			"De-tar file '" + tarFile + "' to '" + deTarDestFolder +
					"' failed. Probably this is caused by source tar file adopting compression or none-gzip compression.", e1);
        } catch (FileNotFoundException  e2) {
        	throw new DeTarFileException(
        			"De-tar file '" + tarFile + "' to '" + deTarDestFolder +
					"' failed. Terminal folder '" + deTarDestFolder + "' didn't exist.", e2);
        } catch (IOException e3) {
			throw new DeTarFileException(
					"De-tar file '" + tarFile + "' to '" + deTarDestFolder +
					"' failed.", e3);
		} finally {  
			if (null != tarArchiveIS) {
				IOUtils.closeQuietly(tarArchiveIS);
			}
			
			if (null != bos) {
				IOUtils.closeQuietly(bos);
			}
			
            deTaring = false;
        }
	}
	
	// 解 tar 結果通知
	private void deTarNotify(File fullPath, String entryName,
					        DeTarResult deTarResult) {		
        if (this.entryListener != null) {        			
			this.entryListener.entryDeTarNotify(fullPath,
												entryName,
												deTarResult);
		} 
	}
	
	private TarArchiveInputStream getTarArchiveInputStream(File tarFile)
											throws IOException {
		// FileNotFoundException
		InputStream inputStream = new FileInputStream(tarFile);
		if (this.usingGZipDecompress) {
			// throws IOException
			inputStream = new GZIPInputStream(inputStream);
		}
		TarArchiveInputStream tarArchiveIS;
		tarArchiveIS = new TarArchiveInputStream(
				new BufferedInputStream(inputStream, bufferLength));		
		return tarArchiveIS;
	}
	
	@Override
	public void interruptDeTarAction() {
		this.isInterruptDeTarAction = true;
	}

	@Override
	public EntryListener getEntryListener() {		
		return this.entryListener;
	}

	@Override
	public DeTarFileChecker getDeTarFileChecker() {		
		return this.deTarFileChecker;
	}

	@Override
	public boolean isUsingGZipDecompress() {
		return this.usingGZipDecompress;
	}

	@Override
	public void removeEntryListener() {
		// 需先停用 CompleteNotifyHandler，再移除 EntryCompleteListener
		if (!this.deTaring) {
			if (this.entryListener != null) {
	    		this.entryListener = null;
	    	}
		}		
	}

	@Override
	public void removeDeTarFileTerminalChecker() {
		if (!this.deTaring) {
			this.deTarFileChecker = null;
		}
	}
		
	@Override
	public void setEntryListener(
			EntryListener entryListener) {
		if (!this.deTaring) {
			this.entryListener = entryListener;
		}
	}

	@Override
	public void setDeTarFileChecker(
			DeTarFileChecker deTarFileChecker) {
		if (!this.deTaring) {
			this.deTarFileChecker = deTarFileChecker;
		}
	}

	@Override
	public void setGZipDecompress(boolean status) {
		if (!this.deTaring) {
			this.usingGZipDecompress = status;
		}		
	}
}
